/**
 * Copyright 厦门中软海晟信息技术有限公司 版权所有 违者必究 2019
 */
package com.opencvjava.lessons2;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import static com.opencvjava.support.util.CvUtils.imshow;
import static com.opencvjava.support.util.CvUtils.mat;
import static org.opencv.core.Core.normalize;
import static org.opencv.core.CvType.CV_8U;
import static org.opencv.imgproc.Imgproc.*;

/**
 * @author : sunzb(sunzb@hsit.com.cn)
 * @date: 2019/1/15
 */
@Service
public class WaterShedsDemo {
    private final Logger logger = LoggerFactory.getLogger(WaterShedsDemo.class);
    public void test() {
        Mat src = mat("waterShed", "cards.png");
        if (src.empty()) {
            throw new RuntimeException("图片读取失败");
        }
        int total = (int) src.total();
        byte[] data = new byte[total * 3];
        src.get(0, 0, data);
        int index;
        for (int i = 0; i < total; i++) {
            index = 3 * i;
            // byte -1 是白色，byte 0 是黑色
            if (data[index] == -1 && data[index + 1] == -1 && data[index + 2] == -1) {
                data[index] = data[index + 1] = data[index + 2] = 0;
            }
        }
        src.put(0, 0, data);
        imshow("源图", src);
        Mat gray = new Mat();
        cvtColor(src, gray, COLOR_BGR2GRAY);
        imshow("灰度", gray);
        Mat binary = new Mat();
        threshold(gray, binary, 127, 255, THRESH_OTSU);
        imshow("二值化", binary);
        Mat dist = new Mat();
        distanceTransform(binary, dist, DIST_L2, 3);
        normalize(dist, dist, 0, 255, Core.NORM_MINMAX);
        Mat dist_8u = new Mat();
        // 转换为彩色图像
        dist.convertTo(dist_8u, CV_8U);
        imshow("距离变换后的图像", dist_8u);
        // 进行二值化和腐蚀,去除尖锐边角和噪声(0.4~1)*255范围进行二值化
        threshold(dist, dist, 102, 255, Imgproc.THRESH_BINARY);
        dist.convertTo(dist_8u, CV_8U);
        imshow("去除边角", dist_8u);
        Mat erode = new Mat();
        Mat kernal = getStructuringElement(MORPH_RECT, new Size(13, 13));
        erode(dist, erode, kernal);
        erode.convertTo(dist_8u, CV_8U);
        imshow("腐蚀", dist_8u);
        // 6.生成marker
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = new Mat();
        findContours(dist_8u, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, new Point(0, 0));
        Mat contourMat = src.clone();
        for (int i = 0; i < contours.size(); i++) {
            drawContours(contourMat, contours, i, new Scalar(0, 255, 0), 5);
        }
        imshow("腐蚀后轮廓", contourMat);
        Mat markers = Mat.zeros(src.size(), CvType.CV_32SC1);
        // 随机颜色数组
        Scalar[] colors = new Scalar[contours.size()];
        Random random = new Random();
        for (int i = 0; i < contours.size(); i++) {
            // 把Marker绘制上
            Imgproc.drawContours(markers, contours, i, new Scalar(i + 1), -1);
            colors[i] = new Scalar(random.nextInt(255), random.nextInt(255), random.nextInt(255));
        }
        Mat mark = new Mat();
        markers.convertTo(mark, CV_8U);
        Core.bitwise_not(mark, mark);
        imshow("markers", mark);
        Imgproc.circle(markers, new Point(5, 5), 3, new Scalar(255), -1);
        markers.convertTo(mark, CV_8U);
        Core.bitwise_not(mark, mark);
        imshow("markers画圈", mark);

        // 7.分水岭变换
        Imgproc.watershed(src, markers);
        markers.convertTo(mark, CV_8U);
        Core.bitwise_not(mark, mark);
        imshow("分水岭变换后的结果", mark);

        // 给各个边框填色
        Mat dst = new Mat(src.size(), CvType.CV_8UC3);
        int[] idxv = new int[1];
        for (int row = 0; row < markers.rows(); row++) {
            for (int col = 0; col < markers.cols(); col++) {
                // 获取各个点的颜色赋值
                markers.get(row, col, idxv);
                if (idxv[0] > 0 && idxv[0] <= contours.size()) {
                    double[] rgb = colors[idxv[0] - 1].val;
                    dst.put(row, col, new byte[]{(byte) rgb[0], (byte) rgb[1], (byte) rgb[2]});
                } else {
                    dst.put(row, col, new byte[]{(byte) 0, (byte) 0, (byte) 0});
                }
            }
        }
        imshow("显示出分割边缘的图像", dst);
    }

    // 完美版
    public void test1() {
        Mat src = mat("waterShed", "cards.png");
        if (src.empty()) {
            throw new RuntimeException("图片读取失败");
        }
        int total = (int) src.total();
        byte[] data = new byte[total * 3];
        src.get(0, 0, data);
        int index;
        for (int i = 0; i < total; i++) {
            index = 3 * i;
            // byte -1 是白色，byte 0 是黑色
            if (data[index] == -1 && data[index + 1] == -1 && data[index + 2] == -1) {
                data[index] = data[index + 1] = data[index + 2] = 0;
            }
        }
        src.put(0, 0, data);
        imshow("源图", src);
        // Create a kernel that we will use to sharpen our image
        Mat kernel = new Mat(3, 3, CvType.CV_32F);
        // an approximation of second derivative, a quite strong kernel
        kernel.put(0, 0, 1, 1, 1, 1, -8, 1, 1, 1, 1);
        Mat imgLaplacian = new Mat();
        Imgproc.filter2D(src, imgLaplacian, CvType.CV_32F, kernel);
        Mat sharp = new Mat();
        src.convertTo(sharp, CvType.CV_32F);
        Mat imgResult = new Mat();
        Core.subtract(sharp, imgLaplacian, imgResult);
        // convert back to 8bits gray scale
        imgResult.convertTo(imgResult, CvType.CV_8UC3);
        imgLaplacian.convertTo(imgLaplacian, CvType.CV_8UC3);
        imshow("拉普拉斯增强", imgResult);
        Mat gray = new Mat();
        cvtColor(imgResult, gray, COLOR_BGR2GRAY);
        imshow("灰度", gray);
        Mat binary = new Mat();
        threshold(gray, binary, 127, 255, THRESH_OTSU);
        imshow("二值化", binary);
        Mat dist = new Mat();
        distanceTransform(binary, dist, DIST_L2, 3);
        normalize(dist, dist, 0, 255, Core.NORM_MINMAX);
        Mat dist_8u = new Mat();
        // 转换为彩色图像
        dist.convertTo(dist_8u, CV_8U);
        imshow("距离变换后的图像", dist_8u);
        // 进行二值化和腐蚀,去除尖锐边角和噪声(0.4~1)*255范围进行二值化
        threshold(dist, dist, 102, 255, Imgproc.THRESH_BINARY);
        dist.convertTo(dist_8u, CV_8U);
        imshow("去除边角", dist_8u);
        /*Mat kernal = getStructuringElement(MORPH_RECT, new Size(5, 5));
        erode(dist, dist, kernal);
        dist.convertTo(dist_8u, CV_8U);
        imshow("腐蚀", dist_8u);*/
        Mat kernal = getStructuringElement(MORPH_RECT, new Size(5, 5));
        dilate(dist, dist, kernal);
        dist.convertTo(dist_8u, CV_8U);
        imshow("膨胀", dist_8u);
        // 6.生成marker
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = new Mat();
        findContours(dist_8u, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, new Point(0, 0));
        Mat contourMat = src.clone();
        for (int i = 0; i < contours.size(); i++) {
            drawContours(contourMat, contours, i, new Scalar(0, 255, 0), 5);
        }
        imshow("腐蚀后轮廓", contourMat);
        Mat markers = Mat.zeros(src.size(), CvType.CV_32SC1);
        // 随机颜色数组
        Scalar[] colors = new Scalar[contours.size()];
        Random random = new Random();
        for (int i = 0; i < contours.size(); i++) {
            // 把Marker绘制上
            Imgproc.drawContours(markers, contours, i, new Scalar(i + 1), -1);
            colors[i] = new Scalar(random.nextInt(255), random.nextInt(255), random.nextInt(255));
        }
        Mat mark = new Mat();
        markers.convertTo(mark, CV_8U);
        Core.bitwise_not(mark, mark);
        imshow("markers", mark);
        Imgproc.circle(markers, new Point(5, 5), 3, new Scalar(255), -1);
        markers.convertTo(mark, CV_8U);
        Core.bitwise_not(mark, mark);
        imshow("markers画圈", mark);

        // 7.分水岭变换
        Imgproc.watershed(src, markers);
        markers.convertTo(mark, CV_8U);
        Core.bitwise_not(mark, mark);
        imshow("分水岭变换后的结果", mark);

        // 给各个边框填色
        Mat dst = new Mat(src.size(), CvType.CV_8UC3);
        int[] idxv = new int[1];
        for (int row = 0; row < markers.rows(); row++) {
            for (int col = 0; col < markers.cols(); col++) {
                // 获取各个点的颜色赋值
                markers.get(row, col, idxv);
                if (idxv[0] > 0 && idxv[0] <= contours.size()) {
                    double[] rgb = colors[idxv[0] - 1].val;
                    dst.put(row, col, new byte[]{(byte) rgb[0], (byte) rgb[1], (byte) rgb[2]});
                } else {
                    dst.put(row, col, new byte[]{(byte) 0, (byte) 0, (byte) 0});
                }
            }
        }
        imshow("显示出分割边缘的图像", dst);
    }

    public void test2() {
        Mat src = mat("waterShed", "cards.png");
        if (src.empty()) {
            throw new RuntimeException("图片读取失败");
        }
        int total = (int) src.total();
        byte[] data = new byte[total * 3];
        src.get(0, 0, data);
        int index;
        for (int i = 0; i < total; i++) {
            index = 3 * i;
            // byte -1 是白色，byte 0 是黑色
            if (data[index] == -1 && data[index + 1] == -1 && data[index + 2] == -1) {
                data[index] = data[index + 1] = data[index + 2] = 0;
            }
        }
        src.put(0, 0, data);
        imshow("源图", src);
        // Create a kernel that we will use to sharpen our image
        Mat kernel = new Mat(3, 3, CvType.CV_32F);
        // an approximation of second derivative, a quite strong kernel
        kernel.put(0, 0, 1, 1, 1, 1, -8, 1, 1, 1, 1);
        Mat imgLaplacian = new Mat();
        Imgproc.filter2D(src, imgLaplacian, CvType.CV_32F, kernel);
        Mat sharp = new Mat();
        src.convertTo(sharp, CvType.CV_32F);
        Mat imgResult = new Mat();
        Core.subtract(sharp, imgLaplacian, imgResult);
        // convert back to 8bits gray scale
        imgResult.convertTo(imgResult, CvType.CV_8UC3);
        imgLaplacian.convertTo(imgLaplacian, CvType.CV_8UC3);
        imshow("New Sharped Image", imgResult);
        // Create binary image from source image
        Mat bw = new Mat();
        Imgproc.cvtColor(imgResult, bw, Imgproc.COLOR_BGR2GRAY);
        Imgproc.threshold(bw, bw, 40, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
        imshow("二值化", bw);
        // Perform the distance transform algorithm
        Mat dist = new Mat();
        distanceTransform(bw, dist, DIST_L2, 3);
        // Normalize the distance image for range = {0.0, 1.0}
        // so we can visualize and threshold it
        normalize(dist, dist, 0.0, 1.0, Core.NORM_MINMAX);
        Mat distDisplayScaled = new Mat();
        Core.multiply(dist, new Scalar(255), distDisplayScaled);
        Mat distDisplay = new Mat();
        distDisplayScaled.convertTo(distDisplay, CvType.CV_8U);
        imshow("距离变换后的图像", distDisplay);
        threshold(dist, dist, 0.4, 1.0, Imgproc.THRESH_BINARY);
        Core.multiply(dist, new Scalar(255), distDisplayScaled);
        distDisplayScaled.convertTo(distDisplay, CvType.CV_8U);
        imshow("去除边角", distDisplay);
        Mat kernel1 = Mat.ones(5, 5, CvType.CV_8U);
        dilate(dist, dist, kernel1);
        /*Mat kernal = getStructuringElement(MORPH_RECT, new Size(5, 5));
        erode(dist, dist, kernal);*/
        Core.multiply(dist, new Scalar(255), distDisplayScaled);
        distDisplayScaled.convertTo(distDisplay, CvType.CV_8U);
        imshow("膨胀", distDisplay);
        Mat dist_8u = new Mat();
        dist.convertTo(dist_8u, CvType.CV_8U);
        // Find total markers
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = new Mat();
        findContours(dist_8u, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
        // Create the marker image for the watershed algorithm
        Mat markers = Mat.zeros(dist.size(), CvType.CV_32S);
        // Draw the foreground markers
        for (int i = 0; i < contours.size(); i++) {
            drawContours(markers, contours, i, new Scalar(i + 1), -1);
        }
        markers.convertTo(distDisplay, CvType.CV_8U);
        Core.bitwise_not(distDisplay, distDisplay);
        imshow("轮廓", distDisplay);
        // Draw the background marker
        Mat markersScaled = new Mat();
        markers.convertTo(markersScaled, CvType.CV_32F);
        Core.normalize(markersScaled, markersScaled, 0.0, 255.0, Core.NORM_MINMAX);
        Imgproc.circle(markersScaled, new Point(5, 5), 3, new Scalar(255, 255, 255), -1);
        Mat markersDisplay = new Mat();
        markersScaled.convertTo(markersDisplay, CvType.CV_8U);
        imshow("Markers", markersDisplay);
        Imgproc.circle(markers, new Point(5, 5), 3, new Scalar(255, 255, 255), -1);
        // Perform the watershed algorithm
        Imgproc.watershed(imgResult, markers);
        Mat mark = Mat.zeros(markers.size(), CvType.CV_8U);
        markers.convertTo(mark, CvType.CV_8UC1);
        Core.bitwise_not(mark, mark);
        // imshow("Markers_v2", mark); // uncomment this if you want to see how the mark
        // image looks like at that point
        // Generate random colors
        Random rng = new Random(12345);
        List<Scalar> colors = new ArrayList<>(contours.size());
        for (int i = 0; i < contours.size(); i++) {
            int b = rng.nextInt(256);
            int g = rng.nextInt(256);
            int r = rng.nextInt(256);
            colors.add(new Scalar(b, g, r));
        }
        // Create the result image
        Mat dst = Mat.zeros(markers.size(), CvType.CV_8UC3);
        byte[] dstData = new byte[(int) (dst.total() * dst.channels())];
        dst.get(0, 0, dstData);
        // Fill labeled objects with random colors
        int[] markersData = new int[(int) (markers.total() * markers.channels())];
        markers.get(0, 0, markersData);
        for (int i = 0; i < markers.rows(); i++) {
            for (int j = 0; j < markers.cols(); j++) {
                index = markersData[i * markers.cols() + j];
                if (index > 0 && index <= contours.size()) {
                    dstData[(i * dst.cols() + j) * 3 + 0] = (byte) colors.get(index - 1).val[0];
                    dstData[(i * dst.cols() + j) * 3 + 1] = (byte) colors.get(index - 1).val[1];
                    dstData[(i * dst.cols() + j) * 3 + 2] = (byte) colors.get(index - 1).val[2];
                } else {
                    dstData[(i * dst.cols() + j) * 3 + 0] = 0;
                    dstData[(i * dst.cols() + j) * 3 + 1] = 0;
                    dstData[(i * dst.cols() + j) * 3 + 2] = 0;
                }
            }
        }
        dst.put(0, 0, dstData);
        // Visualize the final image
        imshow("Final Result", dst);
    }

    public void test3() {
        Mat src = mat("waterShed", "coins.jpg");
        if (src.empty()) {
            throw new RuntimeException("图片读取失败");
        }
        Mat gray = new Mat();
        cvtColor(src, gray, COLOR_BGR2GRAY);
        imshow("灰度", gray);
        Mat filter = new Mat();
        Imgproc.bilateralFilter(gray, filter, 0, 50, 25);
        imshow("双边模糊", filter);
        Mat binary = new Mat();
        threshold(filter, binary, 127, 255, THRESH_OTSU);
        imshow("二值化", binary);
        Mat close = new Mat();
        morphologyEx(binary, close, MORPH_CLOSE, getStructuringElement(MORPH_RECT, new Size(5, 5)));
        imshow("闭操作", close);
        Mat dist = new Mat();
        distanceTransform(close, dist, DIST_L2, 3);
        normalize(dist, dist, 0, 255, Core.NORM_MINMAX);
        Mat dist_8u = new Mat();
        // 转换为彩色图像
        dist.convertTo(dist_8u, CV_8U);
        imshow("距离变换后的图像", dist_8u);
        // 进行二值化和腐蚀,去除尖锐边角和噪声(0.4~1)*255范围进行二值化
        Imgproc.threshold(dist, dist, 102, 255, Imgproc.THRESH_BINARY);
        dist.convertTo(dist_8u, CV_8U);
        imshow("去除边角", dist_8u);
        Mat erode = new Mat();
        Mat kernal = getStructuringElement(MORPH_RECT, new Size(13, 13));
        Imgproc.erode(dist, erode, kernal);
        erode.convertTo(dist_8u, CV_8U);
        imshow("腐蚀", dist_8u);
        // 6.生成marker
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = new Mat();
        findContours(dist_8u, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, new Point(0, 0));
        Mat contourMat = src.clone();
        for (int i = 0; i < contours.size(); i++) {
            drawContours(contourMat, contours, i, new Scalar(0, 255, 0), 5);
        }
        imshow("腐蚀后轮廓", contourMat);
        Mat markers = Mat.zeros(src.size(), CvType.CV_32SC1);
        // 随机颜色数组
        Scalar[] colors = new Scalar[contours.size()];
        Random random = new Random();
        for (int i = 0; i < contours.size(); i++) {
            // 把Marker绘制上
            Imgproc.drawContours(markers, contours, i, new Scalar(i + 1), -1);
            colors[i] = new Scalar(random.nextInt(255), random.nextInt(255), random.nextInt(255));
        }
        Mat mark = new Mat();
        markers.convertTo(mark, CV_8U);
        Core.bitwise_not(mark, mark);
        imshow("markers", mark);
        Imgproc.circle(markers, new Point(5, 5), 3, new Scalar(255), -1);
        markers.convertTo(mark, CV_8U);
        Core.bitwise_not(mark, mark);
        imshow("markers画圈", mark);

        // 7.分水岭变换
        Imgproc.watershed(src, markers);
        markers.convertTo(mark, CV_8U);
        Core.bitwise_not(mark, mark);
        imshow("分水岭变换后的结果", mark);

        // 给各个边框填色
        Mat dst = new Mat(src.size(), CvType.CV_8UC3);
        int[] idxv = new int[1];
        for (int row = 0; row < markers.rows(); row++) {
            for (int col = 0; col < markers.cols(); col++) {
                // 获取各个点的颜色赋值
                markers.get(row, col, idxv);
                if (idxv[0] > 0 && idxv[0] <= contours.size()) {
                    double[] rgb = colors[idxv[0] - 1].val;
                    dst.put(row, col, new byte[]{(byte) rgb[0], (byte) rgb[1], (byte) rgb[2]});
                } else {
                    dst.put(row, col, new byte[]{(byte) 0, (byte) 0, (byte) 0});
                }
            }
        }
        imshow("显示出分割边缘的图像", dst);
    }
}
